package token

import (
	"context"
	"errors"
	"fmt"
	_ "github.com/gogf/gf/contrib/nosql/redis/v2"
	"github.com/gogf/gf/v2/container/gvar"
	"github.com/gogf/gf/v2/crypto/gmd5"
	"github.com/gogf/gf/v2/database/gredis"
	"github.com/gogf/gf/v2/encoding/gjson"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/util/gconv"
	"time"
)

/*
配置内容
sw-token:
  redis:
    # redis地址
    address: "127.0.0.1:6379"
    # redis密码
    password: ""
    # redis库
    db: 5
  # token名称前缀
  token-name: "sw-session"
  # token过期时间，秒，默认30天
  timeout: 2592000
  # 登出类型，1：删除token，2：token过期
  logout-type: 1
  # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)，如果允许一起登录，那么token则一样
  is-concurrent: false
  # 是否自动续期
  is-renewal: true
  # 是否输出操作日志
  is-log: true
*/

var MemberStatus = map[string]int{
	"ok":           99,
	"notLogin":     -1,
	"invalidToken": -2,
	"kickedOut":    -5,
}

var cacheKeys = map[string]string{}

var cacheKeysDefault = map[string]string{
	"session":         ":session:",
	"sessionStatus":   ":status",
	"sessionTokenMap": ":tokenMap",
	"token":           ":token:",
}

type config struct {
	Redis struct {
		Address  string `json:"address"`
		Password string `json:"password"`
		Db       int    `json:"db"`
	} `json:"redis"`
	TokenName    string `json:"token-name"`
	Timeout      int    `json:"timeout"`
	LogoutType   int    `json:"logout-type"`
	IsConcurrent bool   `json:"is-concurrent"`
	IsRenewal    bool   `json:"is-renewal"`
	IsLog        bool   `json:"is-log"`
}

var tokenCache *gredis.Redis
var isLog = false
var allConfig *config
var isInit = false

func setCacheKeyPrefix(prefix string) {
	if isInit {
		return
	}
	for k, v := range cacheKeysDefault {
		cacheKeys[k] = prefix + v
	}
}

// 初始化我自己的redis
func initMyselfRedis(ctx context.Context) error {
	// 处理缓存
	/*redisTmp, err := g.Cfg().Get(ctx, "sw-token.redis", map[string]interface{}{})
	if err != nil {
		printlnEorrorLog("sw-token.redis error ", err)
		return err
	}
	redisConfig := new(gredis.Config)
	if err := gconv.Struct(redisTmp, redisConfig); err != nil {
		printlnEorrorLog("sw-token.redis conv error ", err)
		return err
	}*/
	var err error
	tokenCache, err = gredis.New(&gredis.Config{
		Address: allConfig.Redis.Address,
		Pass:    allConfig.Redis.Password,
		Db:      allConfig.Redis.Db,
	})
	if err != nil {
		printlnEorrorLog("sw-token.redis new error ", err)
		return err
	}
	return nil
}

// 打印错误日志
func printlnEorrorLog(a ...any) {
	g.Log().Error(context.Background(), a)
	//fmt.Println(time.Now().Format("2006-01-02 15:04:05"), a)
}

// 打印日志
func printlnLog(a ...any) {
	if isLog {
		fmt.Println(time.Now().Format("2006-01-02 15:04:05.000"), a)
	}
}

// 统一缓存执行方法
func do(ctx context.Context, command string, args ...interface{}) (*gvar.Var, error) {
	if tokenCache == nil {
		return nil, errors.New("tokenCache is nil")
	}
	return tokenCache.Do(ctx, command, args...)
}

func init() {
	ctx := context.Background()
	printlnLog("[sw-token]初始化")
	configTmp, err := g.Cfg().Get(ctx, "sw-token")
	if err != nil {
		printlnEorrorLog("sw-token init error ", err)
		return
	}
	if configTmp == nil {
		printlnEorrorLog("sw-token init error config is nil")
		return
	}
	allConfig = new(config)
	if err := gconv.Struct(configTmp, allConfig); err != nil {
		printlnEorrorLog("sw-token init conv error ", err)
		return
	}
	setCacheKeyPrefix(allConfig.TokenName)
	err = initMyselfRedis(ctx)
	if err != nil {
		printlnLog("[sw-token]初始化失败，尝试使用gf的redis")
		// 初始化失败，尝试使用gf的redis
		tokenCache = g.Redis()
	}

	isInit = true
	isLog = allConfig.IsLog
}

// GetSessionData 获取session
func GetSessionData(ctx context.Context, memberId int64) map[string]interface{} {
	doVar, err := do(ctx, "GET", cacheKeys["session"]+gconv.String(memberId))
	if err != nil {
		printlnEorrorLog("GetSessionData error1 ", err)
		return map[string]interface{}{}
	}
	if doVar.IsNil() {
		return map[string]interface{}{}
	}
	if doVar.IsEmpty() {
		return map[string]interface{}{}
	}
	content, err := gjson.LoadContent(doVar.String())
	if err != nil {
		return map[string]interface{}{}
	}
	return content.Map()
}

// UpdateSessionData 更新session
func UpdateSessionData(ctx context.Context, memberId int64, data map[string]interface{}) {
	doVar, err := do(ctx, "GET", cacheKeys["session"]+gconv.String(memberId))
	if err != nil {
		printlnEorrorLog("UpdateSessionData error1 ", err)
		return
	}
	if doVar.IsNil() {
		_, err := do(ctx, "SET", cacheKeys["session"]+gconv.String(memberId),
			gjson.MustEncodeString(data))
		if err != nil {
			printlnEorrorLog("UpdateSessionData error2 ", err)
			return
		}
		return
	}
	if doVar.IsEmpty() {
		_, err := do(ctx, "SET", cacheKeys["session"]+gconv.String(memberId),
			gjson.MustEncodeString(data))
		if err != nil {
			printlnEorrorLog("UpdateSessionData error3 ", err)
			return
		}
		return
	}
	content, err := gjson.LoadContent(doVar.String())
	if err != nil {
		return
	}
	for k, v := range data {
		err := content.Set(k, v)
		if err != nil {
			printlnEorrorLog("UpdateSessionData error5 ", err)
			return
		}
	}
	sessionData := content.MustToJsonString()
	_, err = do(ctx, "SET", cacheKeys["session"]+gconv.String(memberId), sessionData)
	if err != nil {
		printlnEorrorLog("UpdateSessionData error4 ", err)
		return
	}
}

// GetMustTokenByMemberId 根据用户id获取token，如果有错误，返回空字符串
func GetMustTokenByMemberId(ctx context.Context, memberId int64) string {
	tokenString, err := GetTokenByMemberId(ctx, memberId)
	if err != nil {
		printlnEorrorLog("GetMustTokenByMemberId error ", err)
		return ""
	}
	return tokenString
}

func GetTokenByMemberId(ctx context.Context, memberId int64) (string, error) {
	doVar, err := do(ctx, "HGET", cacheKeys["sessionTokenMap"], memberId)
	if err != nil {
		return "", err
	}
	if doVar.IsNil() {
		return "", nil
	}
	if doVar.IsEmpty() {
		return "", nil
	}
	return doVar.String(), nil
}

func SetTokenByMemberId(ctx context.Context, memberId int64, tokenString string) {
	_, err := do(ctx, "HSET", cacheKeys["sessionTokenMap"], memberId, tokenString)
	if err != nil {
		printlnEorrorLog("SetTokenByMemberId error ", err)
		return
	}
}

func generateNewToken(ctx context.Context, memberId int64) string {
	// 生成新的token
	tokenStringMd5 := gmd5.MustEncryptString("seawolf-admin-" + gconv.String(time.Now().Unix()) + "-" +
		gconv.String(memberId))

	timeout := allConfig.Timeout
	_, err := do(ctx, "SETEX", cacheKeys["token"]+tokenStringMd5, timeout, gconv.String(memberId))
	if err != nil {
		printlnEorrorLog("generateNewToken error ", err)
		return ""
	}
	_, err = do(ctx, "SETEX", cacheKeys["session"]+gconv.String(memberId), timeout, "{}")
	if err != nil {
		printlnEorrorLog("generateNewToken error2 ", err)
		return ""
	}
	//LoginOkByMemberId(ctx, gconv.String(memberId))
	printlnLog("[sw-token] generateNewToken 生成token memberId", memberId, " token", tokenStringMd5)
	return tokenStringMd5
}

// GenerateLoginToken 生成token
func GenerateLoginToken(ctx context.Context, memberId int64) (string, error) {
	// 判断是否允许一起登录
	tokenStringMd5 := ""
	// 获取老的token
	oldToken, getTokenErr := GetTokenByMemberId(ctx, memberId)

	printlnLog("[sw-token] GenerateLoginToken")

	if !allConfig.IsConcurrent {
		// 不允许一起登录
		// 先删除之前的token
		if len(oldToken) > 0 {
			_, err := do(ctx, "DEL", cacheKeys["token"]+oldToken)
			if err != nil {
				return "", err
			}
			_, err = do(ctx, "HDEL", cacheKeys["sessionTokenMap"], memberId)
			if err != nil {
				return "", err
			}
		}
		// 生成新的token
		tokenStringMd5 = generateNewToken(ctx, memberId)
	} else {
		// 允许一起登录
		// 使用老的token
		if getTokenErr != nil {
			return "", getTokenErr
		}
		// 如果老的token不存在，生成新的token
		if len(oldToken) == 0 {
			tokenStringMd5 = generateNewToken(ctx, memberId)
		} else {
			tokenStringMd5 = oldToken
		}

	}
	// 设置用户登录成功状态
	LoginOkByMemberId(ctx, gconv.String(memberId))
	// 设置token映射表
	SetTokenByMemberId(ctx, memberId, tokenStringMd5)

	return tokenStringMd5, nil
}

// DelayTokenExpire 延期token过期时间
func DelayTokenExpire(ctx context.Context, tokenString string) {
	doVar, err := do(ctx, "GET", cacheKeys["token"]+tokenString)
	if err != nil {
		return
	}
	if doVar.IsNil() {
		return
	}
	if doVar.IsEmpty() {
		return
	}
	DelayTokenExpireTwo(ctx, tokenString, doVar.String())
}

// DelayTokenExpireTwo 延期token过期时间
func DelayTokenExpireTwo(ctx context.Context, tokenString, memberId string) {
	timeout := allConfig.Timeout
	_, err := do(ctx, "EXPIRE", cacheKeys["token"]+tokenString, timeout)
	if err != nil {
		return
	}
	_, err = do(ctx, "EXPIRE", cacheKeys["session"]+memberId, timeout)
	if err != nil {
		return
	}
}

func GetMustMemberIdByToken(ctx context.Context, tokenString string) int64 {
	memberId, err := GetMemberIdByToken(ctx, tokenString)
	if err != nil {
		printlnEorrorLog("GetMustMemberIdByToken error ", err)
		return 0
	}
	return memberId
}

func GetMemberIdByToken(ctx context.Context, tokenString string) (int64, error) {
	doVar, err := do(ctx, "GET", cacheKeys["token"]+tokenString)
	if err != nil {
		printlnEorrorLog("VerifyLoginToken error ", err)
		return 0, err
	}
	if doVar.IsNil() {
		return 0, errors.New("memberId is nil")
	}
	if doVar.IsEmpty() {
		return 0, errors.New("memberId is empty")
	}
	return doVar.Int64(), nil
}

// VerifyLoginToken 验证token
func VerifyLoginToken(ctx context.Context, tokenString string) (int64, error) {
	memberId, err := GetMemberIdByToken(ctx, tokenString)
	if err != nil {
		return 0, err
	}
	// 根据memberId查缓存是否有其他状态
	memberStatus := GetSessionStatus(ctx, gconv.String(memberId))
	if memberStatus < 0 {
		if memberStatus == MemberStatus["notLogin"] {
			return 0, errors.New("member is not login")
		}
		if memberStatus == MemberStatus["invalidToken"] {
			return 0, errors.New("invalid token")
		}
		if memberStatus == MemberStatus["kickedOut"] {
			return 0, errors.New("member is kicked out")
		}
	}
	// 延期token过期时间
	DelayTokenExpireTwo(ctx, tokenString, gconv.String(memberId))
	return memberId, nil
}

// GetSessionStatus 获取用户状态
func GetSessionStatus(ctx context.Context, memberId string) int {
	doVar, err := do(ctx, "HGET", cacheKeys["sessionStatus"], memberId)
	if err != nil {
		printlnEorrorLog("GetSessionStatus error1 ", err)
		return MemberStatus["notLogin"]
	}
	if doVar.IsNil() {
		return MemberStatus["notLogin"]
	}
	if doVar.IsEmpty() {
		return MemberStatus["notLogin"]
	}
	return doVar.Int()
}

// SetSessionStatus 设置用户状态
func SetSessionStatus(ctx context.Context, memberId string, status int) {
	_, err := do(ctx, "HSET", cacheKeys["sessionStatus"], memberId, gconv.String(status))
	if err != nil {
		printlnEorrorLog("SetSessionStatus error1 ", err)
		return
	}
}

// LoginOkByMemberId 设置用户状态登录成功
func LoginOkByMemberId(ctx context.Context, memberId string) {
	printlnLog("[sw-token] LoginOkByMemberId 设置用户状态登录成功", memberId)
	// 设置缓存，标记该用户已经登录
	SetSessionStatus(ctx, memberId, MemberStatus["ok"])
}

// LoginOkByToken 设置用户状态登录成功
func LoginOkByToken(ctx context.Context, tokenString string) {
	memberId, err := GetMemberIdByToken(ctx, tokenString)
	if err != nil {
		printlnEorrorLog("LoginOkByToken error1 ", err)
		return
	}
	if memberId == 0 {
		return
	}
	LoginOkByMemberId(ctx, gconv.String(memberId))
}

// KickoutByMemberId 踢人下线，根据memberId
func KickoutByMemberId(ctx context.Context, memberId string) {
	printlnLog("[sw-token] KickoutByMemberId 踢人下线，根据memberId", memberId)
	// 设置缓存，标记该用户已经被踢下线
	SetSessionStatus(ctx, memberId, MemberStatus["kickedOut"])
}

// KickoutByToken 踢人下线，根据token
func KickoutByToken(ctx context.Context, tokenString string) {
	memberId, err := GetMemberIdByToken(ctx, tokenString)
	if err != nil {
		printlnEorrorLog("KickoutByToken error1 ", err)
		return
	}
	if memberId == 0 {
		return
	}
	KickoutByMemberId(ctx, gconv.String(memberId))
}
func logoutDeleteByMemberId(ctx context.Context, memberId string) {
	if memberId == "" {
		return
	}
	// 先用memberId查token
	memberToken, _ := GetTokenByMemberId(ctx, gconv.Int64(memberId))
	if memberToken != "" {
		// 删除token
		_, err := do(ctx, "DEL", cacheKeys["token"]+memberToken)
		if err != nil {
			printlnEorrorLog("logoutDeleteByMemberId error1 ", err)
			return
		}
	}
	// 删除session
	_, err := do(ctx, "DEL", cacheKeys["session"]+memberId)
	if err != nil {
		printlnEorrorLog("logoutDeleteByMemberId error2 ", err)
		return
	}
	// 删除sessionStatus
	_, err = do(ctx, "HDEL", cacheKeys["sessionStatus"], memberId)
	if err != nil {
		printlnEorrorLog("logoutDeleteByMemberId error3 ", err)
		return
	}
	// 删除tokenMap
	_, err = do(ctx, "HDEL", cacheKeys["sessionTokenMap"], memberId)
	if err != nil {
		printlnEorrorLog("logoutDeleteByMemberId error4 ", err)
		return
	}
}

// LogoutByMemberId 退出登录，根据memberId
func LogoutByMemberId(ctx context.Context, memberId string) {
	printlnLog("[sw-token] LogoutByMemberId 退出登录，根据memberId", memberId)
	if memberId == "" {
		return
	}
	if allConfig.LogoutType == 1 {
		// 删除token
		logoutDeleteByMemberId(ctx, memberId)
	}
	if allConfig.LogoutType == 2 {
		// 设置缓存，标记该用户已经退出登录
		SetSessionStatus(ctx, memberId, MemberStatus["invalidToken"])
	}
}

// LogoutByToken 退出登录，根据token
func LogoutByToken(ctx context.Context, tokenString string) {
	memberId, err := GetMemberIdByToken(ctx, tokenString)
	if err != nil {
		printlnEorrorLog("LogoutByToken error1 ", err)
		return
	}
	if memberId == 0 {
		return
	}
	LogoutByMemberId(ctx, gconv.String(memberId))
}
